Sekvenciranje peptida

Sekvenciranje peptida, utvrđivanje lanca aminokiselina koje grade peptid, vrši se spektrometrijom. Ova metoda omogućava merenje odgovarajućih fizičkih svojstava (mase, naelektrisanja, ...) različitih fragmenata peptida čije vrednosti formiraju spektar. Fragmenti peptida predstavljaju uzastopne podsekvence aminokiselina, pri čemu su poznate vrednosti odabranih fizičkih osobina pojedinačnih aminokiselina (npr. njihove mase). Na osnovu dobijenog spektra, moguće je utvrditi koji redosled aminokiselina može dati takav spektar. U praksi, teorijski, idealni spektar koji ne sadrži greške se ne dešava, usled nesavršenosti metoda merenja vrednosti, pa je zato potrebno uzeti i takve slučajeve u obzir.

In [1]:
# Mase aminokiselina
amino_acid_masses = {
        '': 0,
        'G': 57,
        'A': 71,
        'S': 87,
        'P': 97,
        'V': 99,
        'T': 101,
        'C': 103,
        'I': 113,
        'L': 113,
        'N': 114,
        'D': 115,
        'K': 128,
        'Q': 128,
        'E': 129,
        'M': 131,
        'H': 137,
        'F': 147,
        'R': 156,
        'Y': 163,
        'W': 186,
    }
In [2]:
# Klasa PeptideSequencing će sadržati sve potrebne metode
# za sekvenciranje peptida
class PeptideSequencing:
    def __init__(self, amino_acid_masses):
        self.amino_acid_masses = amino_acid_masses
        
    # Generisanje teorijskog linearnog spektra peptide
    def linear_spectrum(self, peptide):
        n = len(peptide)
        prefix_mass = [0]

        for i in range(n):
            aa = peptide[i]
            mass = self.amino_acid_masses[aa]
            last_mass = prefix_mass[-1]
            prefix_mass.append(mass + last_mass)

        spectrum = [0]

        for i in range(n):
            for j in range(i + 1, n + 1):
                fragment_mass = prefix_mass[j] - prefix_mass[i]
                spectrum.append(fragment_mass)

        spectrum.sort()
        return spectrum
    
    # Generisanje teorijskog cikličnog spektra peptide
    def cyclic_spectrum(self, peptide):
        n = len(peptide)
        prefix_mass = [0]

        for i in range(n):
            aa = peptide[i]
            mass = self.amino_acid_masses[aa]
            last_mass = prefix_mass[-1]
            prefix_mass.append(mass + last_mass)


        spectrum = [0]
        peptide_mass = prefix_mass[-1]

        for i in range(n):
            for j in range(i + 1, n + 1):
                fragment_mass = prefix_mass[j] - prefix_mass[i]
                spectrum.append(fragment_mass)

                if i > 0 and j < n:
                    spectrum.append(peptide_mass - fragment_mass)

        spectrum.sort()
        return spectrum
    
    # Prosirivanje peptida is skupa nadovezivanjem 
    # pojedinačno svih mogućih aminokiselina
    def expand(self, peptides):
        new_peptides = set([])

        for peptide in peptides:
            for aa in self.amino_acid_masses:
                if aa != '':
                    new_peptides.add(f'{peptide}{aa}')

        return new_peptides
    
    # Metod utvrđuje da li manji spektar predstavlja podskup
    # multiskupa koji je reprezentovan većim spektrom
    # (da li se svi elementi iz manjeg spektra nalaze u većem spektru,
    # uzimajući u obzir i duplikate)
    def consistent(self, smaller_spectrum, bigger_spectrum):
        n = len(bigger_spectrum)
        m = len(smaller_spectrum)

        j = 0

        for i in range(n):
            s1 = bigger_spectrum[i]
            s2 = smaller_spectrum[j]

            if s1 == s2:
                j += 1
            elif s1 > s2:
                return False

            if j == m:
                return True

        return False
    
    # Metod izračunava masu peptida
    def mass(self, peptide):
        total_mass = 0

        for aa in peptide:
            total_mass += self.amino_acid_masses[aa]

        return total_mass
    
    # Metod pronalazi peptide čiji se teorijski ciklični spektar
    # poklapa sa zadatim spektrom
    def cyclopeptide_sequencing(self, spectrum):
        peptides = set([''])
        parent_mass = spectrum[-1]

        output = []

        while len(peptides) > 0:
            peptides = self.expand(peptides)
            peptides_to_remove = []

            for peptide in peptides:
                mass = self.mass(peptide,)
                
                if mass == parent_mass:
                    cyclic_spectrum = self.cyclic_spectrum(peptide)
                    
                    if cyclic_spectrum == spectrum:
                        output.append(peptide)
                        
                    peptides_to_remove.append(peptide)

                elif not self.consistent(self.linear_spectrum(peptide), spectrum):
                    peptides_to_remove.append(peptide)

            for peptide in peptides_to_remove:
                peptides.remove(peptide)

        return output
    
    # Metod prebrojava koliko je elemenata manjeg spektra
    # pronađeno u većem spektru, uzimajući u obzir i duplikate
    def score(self, smaller_spectrum, bigger_spectrum):
        n = len(bigger_spectrum)
        m = len(smaller_spectrum)

        j = 0
        i = 0

        intersect = 0

        while i < n:
            s1 = bigger_spectrum[i]
            s2 = smaller_spectrum[j]

            if s1 == s2:
                j += 1
                i += 1
                intersect += 1

            elif s1 < s2:
                i += 1

            elif s2 < s1:
                j += 1

            if j == m:
                j -= 1
                break

        return intersect
    
    # Metod redukuje leaderboard skup tako da sadrži samo
    # peptide sa najboljih N skorova u odnosu na zadati spektar
    def trim(self, leaderboard, spectrum, N):
        mapped_leaderboard = []

        for peptide in leaderboard:
            peptide_score = self.score(self.linear_spectrum(peptide), spectrum)
            mapped_leaderboard.append((peptide, peptide_score))

        n = len(mapped_leaderboard)
        mapped_leaderboard.sort(key=lambda x: x[1], reverse=True)

        i = N

        for i in range(N, n):
            if mapped_leaderboard[i][1] < mapped_leaderboard[i - 1][1]:
                break

        return set([x[0] for x in mapped_leaderboard[:i]])
    
    # Metod pronalazi peptid čiji je ciklični spektar najsličniji zadatom spektru
    # dozvoljavajući (u određenoj meri) greške u spektru, 
    # koje se ogledaju u umetnutim, nedostajućim ili izmenjenim vrednostima spektra
    def leaderboard_cyclopeptide_sequencing(self, spectrum, N):
        leaderboard = set([''])
        parent_mass = spectrum[-1]

        leader_peptide = ''
        leader_score = 0

        while len(leaderboard) > 0:
            leaderboard = self.expand(leaderboard)
            peptides_to_remove = []

            for peptide in leaderboard:
                mass = self.mass(peptide)

                if mass == parent_mass:
                    cyclic_spectrum = self.cyclic_spectrum(peptide)
                    peptide_score = self.score(cyclic_spectrum, spectrum)

                    if peptide_score > leader_score:
                        leader_score = peptide_score
                        leader_peptide = peptide

                elif mass > parent_mass:
                    peptides_to_remove.append(peptide)

            for peptide in peptides_to_remove:
                leaderboard.remove(peptide) 

            leaderboard = self.trim(leaderboard, spectrum, N)

        return leader_peptide
In [3]:
ps = PeptideSequencing(amino_acid_masses)

# Testiranje pronalaženja linearnog teorijskog spektra
ps.linear_spectrum('NQEL')
Out[3]:
[0, 113, 114, 128, 129, 242, 242, 257, 370, 371, 484]
In [4]:
# Testiranje pronalaženja cikličnog teorijskog spektra
ps.cyclic_spectrum('NQEL')
Out[4]:
[0, 113, 114, 128, 129, 227, 242, 242, 257, 355, 356, 370, 371, 484]
In [5]:
# Testiranje proširivanja skupa peptida dodavanjem aminokiselina
ps.expand({'A'})
Out[5]:
{'AA',
 'AC',
 'AD',
 'AE',
 'AF',
 'AG',
 'AH',
 'AI',
 'AK',
 'AL',
 'AM',
 'AN',
 'AP',
 'AQ',
 'AR',
 'AS',
 'AT',
 'AV',
 'AW',
 'AY'}
In [6]:
# Testiranje provere konzistentnosti spektara
smaller = [1,1,3,5]
bigger = [1,2,3,4,5]

print(ps.consistent(smaller, bigger))
print(ps.consistent([1,3,5], bigger))
False
True
In [7]:
# Testiranje izračunavanja mase peptida
ps.mass("NQEL")
Out[7]:
484
In [8]:
# Testiranje metoda koji pronalazi peptide čiji
# ciklični spektar odgovara zadatom spektru
# Napomena, aminokiseline Q i K kao i aminokiseline E i I imaju međusobno iste mase

peptide = "NQEL"
spectrum = ps.cyclic_spectrum(peptide)

ps.cyclopeptide_sequencing(spectrum)
Out[8]:
['ELNK',
 'LEKN',
 'EINQ',
 'LNKE',
 'LNQE',
 'KEIN',
 'LEQN',
 'NLEK',
 'NKEI',
 'IEKN',
 'EKNL',
 'KNLE',
 'NKEL',
 'QNLE',
 'QEIN',
 'KNIE',
 'NLEQ',
 'KELN',
 'EQNL',
 'IEQN',
 'NQEL',
 'ELNQ',
 'EQNI',
 'INKE',
 'NIEK',
 'EINK',
 'NIEQ',
 'QELN',
 'INQE',
 'QNIE',
 'EKNI',
 'NQEI']
In [9]:
ps.score([1,2,3],[1,2,2,2,8])
Out[9]:
2
In [10]:
leaderboard = ['NQE', 'QE', 'KWD', 'EE', 'LL']
spectrum = [0, 113, 114, 128, 129, 227, 242, 242, 257, 355, 356, 370, 371, 484]
N = 3

ps.trim(leaderboard, spectrum, N)
Out[10]:
{'EE', 'KWD', 'NQE', 'QE'}
In [11]:
# Modifikovani ciklični spektar NQELNQELNQEL peptida sa umetnutim pogrešnim, modifikovanim postojećim i izbačenim vrednostima 
# Napomena, aminokiseline Q i K kao i aminokiseline E i I imaju međusobno iste mase
spectrum = [0, 113, 113, 113, 114, 114, 114, 128, 128, 128, 129, 129, 129, 227, 227, 227, 242, 242, 242, 242, 242, 242, 257, 257, 257, 355, 355, 355, 356, 356, 356, 370, 370, 370, 371, 371, 371, 484, 484, 484, 484, 484, 484, 484, 484, 484, 484, 484, 484, 597, 597, 597, 598, 598, 598, 612, 612, 612, 613, 613, 613, 711, 711, 711, 726, 726, 726, 726, 726, 726, 741, 741, 741, 839, 839, 839, 840, 840, 840, 854, 854, 854, 855, 855, 855, 968, 968, 968, 968, 968, 968, 968, 968, 968, 968, 968, 968, 1081, 1081, 1081, 1082, 1082, 1082, 1096, 1096, 1096, 1097, 1097, 1097, 1195, 1195, 1195, 1210, 1210, 1210, 1210, 1210, 1210, 1225, 1225, 1225, 1323, 1323, 1323, 1324, 1324, 1324, 1338, 1338, 1338, 1339, 1339, 1339, 1452]

N = 5
ps.leaderboard_cyclopeptide_sequencing(spectrum, N)
Out[11]:
'KELNKELNQEIN'